package dtdrelacional;

import java.io.*;
import java.util.*;
import net.n3.nanoxml.*;

/**
 *
 * @author Jacob
 */
public class GeneradorScriptInserciones {

  HashMap<String, TablaSQL> tablasSQL;
  HashMap<String, RelacionER> relacionesER;
//  Writer archSQL;

  public GeneradorScriptInserciones(HashMap<String, TablaSQL> tablasSQL,
          HashMap<String, RelacionER> relacionesER) {
    this.tablasSQL = tablasSQL;
    this.relacionesER = relacionesER;
  }

  public void generar(String nombreArchXML, String nombreArchSQL) throws Exception {

    IXMLParser parser = XMLParserFactory.createDefaultXMLParser();
    IXMLReader reader = StdXMLReader.fileReader(nombreArchXML);
    parser.setReader(reader);
    IXMLElement raiz_xml = (IXMLElement) parser.parse();

    Enumeration hijos_raiz_xml = raiz_xml.enumerateChildren();
    IXMLElement hijo_xml;
    while (hijos_raiz_xml.hasMoreElements()) {
      hijo_xml = (IXMLElement) hijos_raiz_xml.nextElement();
      // chequeo si la relacion existe en mis estructuras
      if (relacionesER.containsKey(hijo_xml.getName())) {
        procesarRelacionXML(hijo_xml);
      } else if (tablasSQL.containsKey(hijo_xml.getName())) {
        procesarEntidadXML(hijo_xml, null);
      } else {
        // TODO error, el elemento xml no corresponde con relaciones o entidades
      }
    }
    escribirScript(nombreArchSQL);
    System.out.println("Done.\n");
  }

  private void procesarRelacionXML(IXMLElement relacionXML) {

    RelacionER relacionER = relacionesER.get(relacionXML.getName());
    if (relacionER.getAbsorbida() == null) {
      insertarRelacionNoAbsorbida(relacionER, relacionXML);
    } else {
      insertarRelacionAbsorbida(relacionER, relacionXML);
    }
  }

  private void insertarRelacionNoAbsorbida(RelacionER relacionER,
          IXMLElement relacionXML) {

    TablaSQL relacionSQL = tablasSQL.get(relacionER.getNombreRelacion());
    HashMap<String, String> filaSQL = new HashMap<String, String>();

    Enumeration enuHijosXML = relacionXML.enumerateChildren();
    IXMLElement hijoXML;
    while (enuHijosXML.hasMoreElements()) {
      hijoXML = (IXMLElement) enuHijosXML.nextElement();
      if (relacionER.hasEntidad(hijoXML.getName())) {
        // hijoXML es entidad
        // TODO chequear errores de participacion antes de agregar la entidad

        procesarEntidadXML(hijoXML, null);

        // inserto la referencia a la entidad en la fila de la tabla relacion
        TablaSQL entidadSQL = tablasSQL.get(hijoXML.getName());
        String nombreClave = entidadSQL.getColumnasPrimarias().iterator().next().getNombre();

        filaSQL.put(nombreClave, hijoXML.getAttribute(nombreClave, ""));
      } else if (relacionER.hasAtributo(hijoXML.getName())) {
        // hijoXML es atributo
        filaSQL.put(hijoXML.getName(), hijoXML.getContent());
      } else {
        // TODO error, el elemento no corresponde a la relacion
      }
    }
    // agrego la fila a la tabla
    relacionSQL.addFila(filaSQL);
  }

  private void insertarRelacionAbsorbida(RelacionER relacionER,
          IXMLElement relacionXML) {

    TablaSQL tablaAbsorbio = tablasSQL.get(relacionER.getAbsorbida());
    IXMLElement entidadXMLAbsorbio =
            relacionXML.getFirstChildNamed(relacionER.getAbsorbida());

    if (entidadXMLAbsorbio == null) {
      // TODO error, falta entidad
      return;
    }

    String nombreClave = tablaAbsorbio.getColumnasPrimarias().iterator().next().getNombre();
    String valorClave = entidadXMLAbsorbio.getAttribute(nombreClave, "");
    HashMap<String, String> filaSQL =
            tablaAbsorbio.getFilaConClave(nombreClave, valorClave);

    if (filaSQL == null) {
      // la entidadXMLAbsorbio no existe en la tabla, creo una nueva fila
      filaSQL = new HashMap<String, String>();
      procesarEntidadXML(entidadXMLAbsorbio, filaSQL);
    }

    Enumeration enuHijosXML = relacionXML.enumerateChildren();
    IXMLElement hijoXML;
    while (enuHijosXML.hasMoreElements()) {
      hijoXML = (IXMLElement) enuHijosXML.nextElement();
      if (tablaAbsorbio.getNombre().equals(hijoXML.getName())) {
        // ya se proceso antes
        continue;
      } else if (relacionER.hasEntidad(hijoXML.getName())) {
        // hijoXML es entidad
        procesarEntidadXML(hijoXML, null);

        // inserto la referencia a la entidad en la fila de la tabla que absorbio
        TablaSQL entidadSQL = tablasSQL.get(hijoXML.getName());
        nombreClave = entidadSQL.getColumnasPrimarias().iterator().next().getNombre();
        filaSQL.put(nombreClave, hijoXML.getAttribute(nombreClave, ""));
      } else if (relacionER.hasAtributo(hijoXML.getName())) {
        // hijoXML es atributo
        filaSQL.put(hijoXML.getName(), hijoXML.getContent());
      } else {
        // TODO error, el elemento no corresponde a la relacion
      }
    }
  }

  private void procesarEntidadXML(IXMLElement entidadXML,
          HashMap<String, String> filaSQL) {

    if (filaSQL == null) {
      filaSQL = new HashMap<String, String>();
    }

    TablaSQL tablaSQL = tablasSQL.get(entidadXML.getName());

    // Cheque si la entidad ya existe en la tabla
    String nombreClave = tablaSQL.getColumnasPrimarias().iterator().next().getNombre();
    String valorClave = entidadXML.getAttribute(nombreClave, null);
    if (tablaSQL.getFilaConClave(nombreClave, valorClave) != null) {
      // La entidad ya existe en la tabla
      return;
    }

    // agrego los atributos del elemento XML
    insertarAtributosXML(entidadXML.getAttributes(), tablaSQL, filaSQL);

    if (entidadXML.hasChildren()) {
      procesarHijosXMLEntidad(entidadXML, tablaSQL, filaSQL, entidadXML.getName());
    } else {
      filaSQL.put(entidadXML.getName(), entidadXML.getContent());
    }

    tablaSQL.addFila(filaSQL);
  }

  private void insertarAtributosXML(Properties attributes, TablaSQL tablaSQL,
          HashMap<String, String> filaSQL) {

    Enumeration nombresAtributos = attributes.propertyNames();
    String nombreAtributo;
    String valorAtributo;
    while (nombresAtributos.hasMoreElements()) {
      nombreAtributo = (String) nombresAtributos.nextElement();
      if (tablaSQL.hasColumna(nombreAtributo)) {
        valorAtributo = attributes.getProperty(nombreAtributo);
        filaSQL.put(nombreAtributo, valorAtributo);
      } else {
        // TODO error, el atributo no es parte de la tabla
      }
    }
  }

  private void procesarHijosXMLEntidad(IXMLElement hijoXML, TablaSQL tablaPadre,
          HashMap<String, String> filaPadre, String sufijo) {

    Enumeration hijosXML = hijoXML.enumerateChildren();
    IXMLElement hijoXMLAux;
    while (hijosXML.hasMoreElements()) {
      hijoXMLAux = (IXMLElement) hijosXML.nextElement();
      procesarHijoXMLEntidad(hijoXMLAux, tablaPadre, filaPadre, sufijo);
    }
  }

  private void procesarHijoXMLEntidad(IXMLElement hijoXML, TablaSQL tablaPadre,
          HashMap<String, String> filaPadre, String sufijo) {

    if (tablaPadre.hasColumna(hijoXML.getName())) {
      filaPadre.put(hijoXML.getName(), hijoXML.getContent());

    } else if (tablasSQL.containsKey(hijoXML.getName() + "_" + sufijo)
            || tablasSQL.containsKey(hijoXML.getName())) {
      // hijoXML es multivaluado

      // creo una nueva fila para la tabla hijo
      HashMap<String, String> filaHijo = new HashMap<String, String>();
      // coloco las referencias al padre
      Iterator<ColumnaSQL> clavesPKTablaPadre = tablaPadre.getColumnasPrimarias().iterator();
      String clavePK;
      while (clavesPKTablaPadre.hasNext()) {
        clavePK = clavesPKTablaPadre.next().getNombre();
        filaHijo.put(clavePK, filaPadre.get(clavePK));
      }

      TablaSQL tablaHijo = tablasSQL.get(hijoXML.getName() + "_" + sufijo);
      if (tablaHijo == null) {
        tablaHijo = tablasSQL.get(hijoXML.getName());
      }

      if (hijoXML.hasChildren()) {
        procesarHijosXMLEntidad(hijoXML, tablaHijo, filaHijo, hijoXML.getName() + "_" + sufijo);
      } else {
        filaHijo.put(hijoXML.getName(), hijoXML.getContent());
      }

      tablaHijo.addFila(filaHijo);

    } else if (hijoXML.hasChildren()) {
      // en caso de que se hayan aplanado los atributos
      procesarHijosXMLEntidad(hijoXML, tablaPadre, filaPadre, hijoXML.getName() + "_" + sufijo);
    }
  }

  private void escribirScript(String nombreArchSQL) {

    Writer archSQL;
    try {
      archSQL = new FileWriter(nombreArchSQL);
    } catch (IOException ex) {
      System.err.println("Error al abrir archivo de esquema: " + nombreArchSQL);
      return;
    }

    Iterator<TablaSQL> iteTablas = tablasSQL.values().iterator();
    TablaSQL tabla;
    while (iteTablas.hasNext()) {
      tabla = iteTablas.next();

      Iterator<HashMap<String, String>> iteFilas = tabla.getFilas().iterator();
      HashMap<String, String> filaSQL;
      while (iteFilas.hasNext()) {
        filaSQL = iteFilas.next();
        String columns = "INSERT INTO " + tabla.getNombre() + " (";
        String values = "VALUES (";

        Iterator<String> columnaSQL = filaSQL.keySet().iterator();
        String nombreColumna;
        while (columnaSQL.hasNext()) {
          nombreColumna = columnaSQL.next();
          columns += nombreColumna;
          // TODO escapar los apostrofes y ampersands
          values += "'" + filaSQL.get(nombreColumna) + "'";
          if (columnaSQL.hasNext()) {
            columns += ", ";
            values += ", ";
          }
        }
        try {
          archSQL.write(columns + ")\n");
          archSQL.write(values + ");\n");
        } catch (IOException ex) {
          System.err.println("Error al escribir en archivo de esquema: " + nombreArchSQL);
        }
      }
      // Termina la tabla
      try {
        archSQL.write("\n");
      } catch (IOException ex) {
        System.err.println("Error al escribir en archivo de esquema: " + nombreArchSQL);
      }
    }

    // Cierro el archivo
    try {
      archSQL.close();
    } catch (IOException ex) {
      System.err.println("Error al cerrar archivo de esquema: " + nombreArchSQL);
    }
  }

  private void escribirScriptVariosArchivos(String nombreArchSQL) {

    Iterator<TablaSQL> iteTablas = tablasSQL.values().iterator();
    TablaSQL tabla;
    while (iteTablas.hasNext()) {
      tabla = iteTablas.next();

      // Creo el archivo para la tabla
      String nombreArchTablaSQL = nombreArchSQL.substring(0, nombreArchSQL.lastIndexOf('.'))
              + "_" + tabla.getNombre() + ".sql";
      Writer archSQL;
      try {
        archSQL = new FileWriter(nombreArchTablaSQL);
      } catch (IOException ex) {
        System.err.println("Error al abrir archivo de esquema: " + nombreArchTablaSQL);
        return;
      }

      try {
        Iterator<HashMap<String, String>> iteFilas = tabla.getFilas().iterator();
        HashMap<String, String> filaSQL;
        while (iteFilas.hasNext()) {
          filaSQL = iteFilas.next();
          String columns = "INSERT INTO " + tabla.getNombre() + " (";
          String values = "VALUES (";

          Iterator<String> columnaSQL = filaSQL.keySet().iterator();
          String nombreColumna;
          while (columnaSQL.hasNext()) {
            nombreColumna = columnaSQL.next();
            columns += nombreColumna;
            values += "'" + filaSQL.get(nombreColumna) + "'";
            if (columnaSQL.hasNext()) {
              columns += ", ";
              values += ", ";
            }
          }
          archSQL.write(columns + ")\n");
          archSQL.write(values + ");\n");
        }
        archSQL.write("\n");

      } catch (IOException ex) {
        System.err.println("Error al escribir en archivo de esquema: " + nombreArchTablaSQL);
      }

      // Cierro el archivo de la tabla
      try {
        archSQL.close();
      } catch (IOException ex) {
        System.err.println("Error al cerrar archivo de esquema: " + nombreArchTablaSQL);
      }
    }
  }
}
